﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using ExpressionEvaluator.Expceptions;
using System.Reflection;
using System.Collections;
using ExpressionEvaluator.Anonymous;

namespace ExpressionEvaluator.BuilderTreeNode
{
	/// <summary>
	/// Node contains create new instance expression. 
	/// </summary>
	internal class NewInstanceNode : ExpressionNode
	{
		/// <summary>
		/// Gets or sets the start bracket.
		/// </summary>
		/// <value>The start bracket.</value>
		public CodeBlock StartBracket
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the end bracket.
		/// </summary>
		/// <value>The end bracket.</value>
		public CodeBlock EndBracket
		{
			get;
			set;
		}

		/// <summary>
		/// Gets the parameter nodes.
		/// </summary>
		/// <returns>Iterator for parameter nodes.</returns>
		private IEnumerable<BuilderTreeNodeBase> GetParameterNodes()
		{
			for (int i = 0; i < Nodes.Count; i++)
			{
				if (!(Nodes[i] is OpaqueExpressionNode))
				{
					yield return Nodes[i];
				}
				else
				{
					break;
				}
			}
		}

		/// <summary>
		/// Gets the opaque nodes.
		/// </summary>
		/// <returns>Iterator for opaque nodes.</returns>
		private IEnumerable<OpaqueExpressionNode> GetOpaqueNodes()
		{
			for (int i = 0; i < Nodes.Count; i++)
			{
				if ((Nodes[i] is OpaqueExpressionNode))
				{
					yield return (Nodes[i] as OpaqueExpressionNode);
				}
			}
		}


		/// <summary>
		/// Gets the expression.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <returns>Expression</returns>
		public override Expression GetExpression(BuilderTree tree)
		{
			string typeName = string.Empty;
			// get type name
			Idents.ForEach(code => typeName += code.Text);
			Type type;
            bool hasInitializationNodes = false;
            // check if type name is anonymous (starts with <> or ident is string.empty)
            if (Idents[0].Text.StartsWith("<>") || (Idents[0].Text == string.Empty))
            {
                type = CreateAnonymousType(tree);
                hasInitializationNodes = true;
            }
			// if only single type name => try to find in registered namespaces
			else if (Idents.Count == 1)
			{
				type = TryToFindTypeInRegisteredNamespaces(typeName, tree);
			}
			// else try find in registered assemblies
			else
			{
				type = TryToFindTypeInRegisteredAssemblies(typeName, tree);
			}
			// error if nothing founded
			if (type == null)
			{
				throw new EvalException(string.Format("Unable to lookup for type '{0}'!", typeName), StartBracket);
			}
			// if no subnodes, then call simple constructor
			if (Nodes.Count == 0)
			{
				return Expression.New(type);
			}
			else
			{
				Expression currentExpression = null;
				List<Expression> arguments = new List<Expression>();
				List<Type> argumentsTypes = new List<Type>();
				// get parameter nodes
				foreach (var node in GetParameterNodes())
				{
					Expression nodeExpression;
                    // for anonymous types with initialization nodes (like Text = "some value") get second node in current node
                    if (hasInitializationNodes && node.Nodes.Count > 0)
                    {
                        nodeExpression = node.Nodes[1].GetExpression(tree);
                    }
                    else
                    {
                        nodeExpression = node.GetExpression(tree);
                    }
					arguments.Add(nodeExpression);
					argumentsTypes.Add(nodeExpression.Type);
				}
				// create new expression based on parameters
				currentExpression = CreateNewExpression(type, arguments, argumentsTypes);
				// call all opaque nodes to finish expression
				foreach (var node in GetOpaqueNodes())
				{
					currentExpression = node.GetParentExpression(currentExpression, tree);
				}
				return currentExpression;
			}
		}

        /// <summary>
        /// Creates anonymous type.
        /// </summary>
        /// <param name="tree">The tree.</param>
        /// <returns>Anonymous type creator.</returns>
        private Type CreateAnonymousType(BuilderTree tree)
        {
            string anonymousTypeName;
            if (Idents[0].Text != string.Empty)
            {
                // for name remove part after `number
                anonymousTypeName = Idents[0].Text.Substring(0, Idents[0].Text.Length - 2);
            }
            else
            {
                // generate unique anonymous type name in builder tree
                anonymousTypeName = tree.GetNewAnonymousTypeName(GetParameterNodes().Count());
            }
            return AnonymousTypeBuilder.BuildType(
                anonymousTypeName,
                GetParameterNodes().Select(node => 
                    // if no explicit name use last ident
                    (node.Nodes.Count == 0 ) ? node.Idents[node.Idents.Count - 1].Text
                    // else use explicit name
                    : node.Nodes[0].Idents[0].Text).ToArray());
        }

		/// <summary>
		/// Creates the new expression.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="arguments">The arguments.</param>
		/// <param name="argumentsTypes">The arguments types.</param>
		/// <returns></returns>
		private static Expression CreateNewExpression(Type type, List<Expression> arguments, List<Type> argumentsTypes)
		{
            
            // if generic type definition => anonymous type and thus use getter in expression
            if (type.IsGenericTypeDefinition)
            {
                type = type.MakeGenericType(argumentsTypes.ToArray());
                // get constructor info for specified types
                ConstructorInfo constructorInfo = type.GetConstructor(argumentsTypes.ToArray());
                MemberInfo[] getProperties = type.GetProperties().Select(prop => prop.GetGetMethod()).ToArray();
                return Expression.New(constructorInfo, arguments, getProperties);
            }
            else
            {
                // get constructor info for specified types
                ConstructorInfo constructorInfo = type.GetConstructor(argumentsTypes.ToArray());
                return Expression.New(constructorInfo, arguments);
            }
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </returns>
		/// <remarks>
		/// Can be used for tracing bugs.
		/// </remarks>
		public override string ToString()
		{
			StringBuilder result = new StringBuilder("new ");
			Idents.ForEach(code => result.Append(code.Text));

			result.Append("(");
			int i = 0;
			foreach (var node in GetParameterNodes())
			{
				if (i > 0)
				{
					result.Append(", ");
				}
				result.Append(node.ToString());
				++i;
			}
			result.Append(")");
			foreach (var node in GetOpaqueNodes())
			{
				result.Append(node.ToString());
			}
			return result.ToString();
		}
	}
}
